#include "thread.h"
#include <assert.h>
#include <valgrind/valgrind.h>

ucontext_t * uc_current;
thread_t current_thread;
thread_t main_thread;
int id = 1;
struct list * thread_FIFO_READY = NULL;
struct list * thread_FIFO_WAIT = NULL;
struct list * thread_FIFO_KILLED = NULL;
int init = 0;

void init_main_thread() {
	init++;
	main_thread = malloc(sizeof(struct thread_t));
	main_thread->context = malloc(sizeof(ucontext_t));
	main_thread->context->uc_stack.ss_sp = malloc(main_thread->context->uc_stack.ss_size);
	main_thread->id = 0;
	main_thread->st = RUNNING;
	main_thread->retval = NULL; 
	main_thread->pere = NULL; 
	setcurrent(main_thread);
}

void setcurrent(thread_t current) {
	current_thread = current;
	uc_current = current->context;
}

void setid(thread_t thr) {
	thr->id = id++;
}

void setstatus(enum status st) {
	current_thread->st = st;
}

/* recuperer l'identifiant du thread courant.
 */
extern thread_t thread_self(void) {
	if(!init) {
		init_main_thread();
	}
	return current_thread; 
}

/* creer un nouveau thread qui va exÃ©cuter la fonction func avec l'argument funcarg.
 * renvoie 0 en cas de succÃ¨s, -1 en cas d'erreur.
 */
extern int thread_create(thread_t *newthread, void *(*func)(void *), void *funcarg) {
	if(thread_FIFO_READY == NULL)
		thread_FIFO_READY = create();

	ucontext_t uc_save;
	*newthread = malloc(sizeof(struct thread_t ));
	(*newthread)->context = malloc(sizeof(ucontext_t));
	if(!init) {
		init_main_thread();
	}

	getcontext((*newthread)->context);
	(*newthread)->context->uc_stack.ss_size = 64*1024;
	(*newthread)->context->uc_stack.ss_sp = malloc((*newthread)->context->uc_stack.ss_size);
	(*newthread)->valgrind_stackid = VALGRIND_STACK_REGISTER((*newthread)->context->uc_stack.ss_sp,(*newthread)->context->uc_stack.ss_sp + (*newthread)->context->uc_stack.ss_size);
	(*newthread)->context->uc_link = &uc_save;
	(*newthread)->pere =  NULL ;
	(*newthread)->st = READY;
	makecontext((*newthread)->context,(void * (*)(void *)) func, 1, funcarg);
	(*newthread)->retval = NULL; 

	setid(*newthread);
	thread_FIFO_READY = push(thread_FIFO_READY, *newthread);
	return E_SUCCESS; 
}

/* passer la main Ã  un autre thread.
 */
extern int thread_yield(void) {

	ucontext_t * uc_save = uc_current;
	if(!init) {
		init_main_thread();
	}

	switch(thread_self()->st) {
		case RUNNING :
			setstatus(READY);
			if(thread_FIFO_READY== NULL)
				thread_FIFO_READY = create();
			thread_FIFO_READY = push(thread_FIFO_READY, thread_self());
			break;
		case WAIT :
			if(thread_FIFO_WAIT== NULL)
				thread_FIFO_WAIT = create();

      if(!isIn(thread_FIFO_WAIT, thread_self()->id))
		  	thread_FIFO_WAIT = push(thread_FIFO_WAIT, thread_self());
			break;
		case KILLED : 
			if(thread_FIFO_KILLED == NULL)
				thread_FIFO_KILLED = create();

      if(!isIn(thread_FIFO_KILLED, thread_self()->id))
        thread_FIFO_KILLED = push(thread_FIFO_KILLED, thread_self());
			break;
	}

	thread_t tmp = pop(thread_FIFO_READY);
	if(thread_FIFO_READY->size == 0){
		freeFIFO(thread_FIFO_READY);
		thread_FIFO_READY = NULL;
	}

	setcurrent(tmp);

	setstatus(RUNNING);

	assert(swapcontext(uc_save, uc_current) != -1); 
	return E_SUCCESS;
}


/* attendre la fin d'exÃ©cution d'un thread.
 * la valeur renvoyÃ©e par le thread est placÃ©e dans *retval.
 * si retval est NULL, la valeur de retour est ignorÃ©e.
 */
extern int thread_join(thread_t thread, void **retval) {
	if(!init) {
		init_main_thread();
	}
	//ICI le thread Ã  deja termine quand on rentre dans le thread join
	if(thread->st == KILLED) {
		if(retval != NULL)
			memcpy(retval,&thread->retval, sizeof(void*));
	  int ret = remove_thread(thread_FIFO_KILLED, thread->id); 
    if(ret != E_FAILURE)
      freeThread(thread);		
		//si thread_number==1 alors cela signifie qu'il n'y a plus que le main
		if(thread_FIFO_KILLED->size == 0) {
			freeFIFO(thread_FIFO_KILLED);
			thread_FIFO_KILLED = NULL;
		}
		return E_SUCCESS; 
	}

	//ICI le thread en parametre n'est pas termine donc on bascule vers un autre thread
	thread_t tmp = thread_self();
	setstatus(WAIT);
	thread->pere = tmp;

	//Il faut maintenant liberer la memoire du la structure thread_t
	while(thread->st != KILLED)
		thread_yield();	

	if(retval != NULL)
		memcpy(retval,&thread->retval, sizeof(void*));
	int ret = remove_thread(thread_FIFO_KILLED, thread->id); 
    if(ret != E_FAILURE)
      freeThread(thread);	
	//si thread_number==1 alors cela signifie qu'il n'y a plus que le main
	if(thread_FIFO_KILLED->size == 0){
		freeFIFO(thread_FIFO_KILLED);
		thread_FIFO_KILLED = NULL;
	}


	if(thread_FIFO_WAIT == NULL &&
   thread_FIFO_READY == NULL)
   {
		free(main_thread->context->uc_stack.ss_sp);
		free(main_thread->context);
		free(main_thread);
    init = 0;
	}
	return E_SUCCESS; 
}

/* terminer le thread courant en renvoyant la valeur de retour retval.
 * cette fonction ne retourne jamais.
 */
extern void thread_exit(void *retval) {
	if(!init) {
		init_main_thread();
	}
	thread_self()->retval = retval;
	thread_self()->st = KILLED;
	if(thread_FIFO_KILLED == NULL)
		thread_FIFO_KILLED = create();

	thread_FIFO_KILLED = push(thread_FIFO_KILLED , thread_self());
	thread_t tmp = thread_self();
	if(tmp->pere != NULL){
		int ret = remove_thread(thread_FIFO_WAIT, tmp->pere->id); 
		if(thread_FIFO_WAIT->size == 0){
			freeFIFO(thread_FIFO_WAIT);
			thread_FIFO_WAIT = NULL;
		}
		if(thread_FIFO_READY== NULL)
			thread_FIFO_READY = create();
    
    if(ret != E_FAILURE) {
		  tmp->pere->st = READY;
      thread_FIFO_READY = push(thread_FIFO_READY, tmp->pere);
    }
	}
	thread_self()->pere = NULL;
	thread_yield();
}

